# _*_ coding:utf-8 _*_
# @Time :2022/8/12 10:18
# @File :  producer_yuqidong.py
# @Author :QiKangwei
import binascii
import logging
import struct
import threading
import time
import traceback
from datetime import datetime

import numpy as np
import pandas as pd
import requests
import serial
from enum import Enum

from api.serial import mould, sample

from detecter import settings
from detecter.settings import frame_rate, MACHINE_CODE

logger = logging.getLogger('serial.producer_yuqidong')


class SampleProducer_yuqidong(threading.Thread):
    __instance = None

    def __init__(self, port, baud, pressure_frame_size, temp_frame_size):
        super().__init__()
        self.setName('SampleProducer_yuqidong')
        self.__running = threading.Event()  # 用于停止线程的标识
        self.__data_bytes = bytearray()  # 全局字节数组,串口数据缓冲区
        self.__pressure_frame_size = pressure_frame_size
        self.__temp_frame_size = temp_frame_size
        self.__pressure_frame_list = []  # 压力帧数据列表
        self.__temp_frame_list = []  # 温度帧数据列表
        self.__sample_list = []  # 样本队列
        self.__sample_list1 = []  # 样本队列
        self.__port = serial.Serial(port, baud)

    @classmethod
    def new_instance(cls):
        cls.__instance = SampleProducer_yuqidong(settings.PORT_PRODUCT, settings.BAUD_RATE_PRODUCT, settings.PRESSURE_FRAME_SIZE, settings.TEMP_FRAME_SIZE)
        return cls.__instance

    @classmethod
    def get_instance(cls):
        return cls.__instance

    def get_sample_list(self):
        return self.__sample_list

    def get_sample_list1(self):
        return self.__sample_list1

    def is_producer_running(self):
        return self.__running.is_set()

    def begin(self):
        if not self.is_alive():
            self.__running.set()
            self.__port.close()
            if not self.__port.isOpen():
                self.__port.open()
            self.setDaemon(False)  # 不设置守护线程, 就算websocket挂了也继续收集数据,处理数据, 存入临时文件中, 直到停止本工单才算停止此线程
            self.start()

    def stop(self):
        if self.is_alive():
            self.__running.clear()  # 设置为False
            self.__port.close()
            self.__port = None

    def run(self):
        self.__port.close()
        if not self.__port.isOpen():
            try:
                self.__port.open()
            except:
                logger.info("producer线程串口无法打开，请检查串口设备号")
        data_bytes = self.__data_bytes
        pressure_size = self.__pressure_frame_size
        temp_size = self.__temp_frame_size
        is_first_sample = True
        data_flag = False
        frame_bytes_list = []  # 异常帧数据列表
        last_50_cycles = []  # 存最近50个周期，以用作计算标准周期
        zhenshu = 10  # 测试帧数
        standard_cycle = 0
        sample_id = -1
        while self.__running.is_set():
            current_mould = mould.CurrentMould.get_instance()
            logger.info("样本采集线程sample_producer_yuqidong开始采集数据！！！")
            while current_mould is None:
                current_mould = mould.CurrentMould.get_instance()
                count = self.__port.inWaiting()  # 返回接收缓存中的字节数
                try:
                    temp_bytes = self.__port.read(count)
                except serial.SerialException as e:
                    return None
                except TypeError as e:
                    self.__port.close()
                    return None
                # print('temp_bytes：：：{}'.format(temp_bytes))
                # print(len(temp_bytes))
                data_bytes = data_bytes + temp_bytes
                while len(data_bytes) >= int(pressure_size / 2):
                    if not data_flag:
                        logger.info("sample_producer_yuqidong received data!")
                        data_flag = True
                    frame_bytes = data_bytes[0:int(pressure_size / 2)]
                    frame_bytes = frame_bytes.hex()
                    # print(frame_bytes)
                    if self.calc_crc(frame_bytes[:-4]) == frame_bytes[-4:]:
                        frame_arr = self.transfer_pressure_frame_to_arr(frame_bytes)
                        # print(frame_arr)
                        self.__pressure_frame_list.append(frame_arr)

                        # 检测缓存数据量，如果大于10倍标准周期帧数量，则清理压力帧数据缓存列表
                        if len(self.__pressure_frame_list) >= frame_rate * 60 * 30:  # 一秒80帧  一分钟60秒  以30分钟做限制
                            self.__pressure_frame_list = []
                            last_50_cycles = []
                            is_first_sample = True
                            logger.info("清理缓存")
                            continue

                        frame_list_len = len(self.__pressure_frame_list)
                        if frame_list_len >= 5:
                            check_frame_list = self.__pressure_frame_list[frame_list_len - 5:frame_list_len]
                            if self.is_product_begin(check_frame_list):
                                sample_frame_list = self.__pressure_frame_list[:frame_list_len - 4]  # 包含最后一帧

                                if is_first_sample:
                                    is_first_sample = False
                                    logger.info("采集到第一个样本，不计入，跳过")
                                else:

                                    try:
                                        if len(last_50_cycles) < zhenshu:
                                            logger.info("周期列表个数{}，未到{}，周期帧数不做限制".format(len(last_50_cycles), zhenshu))
                                            last_50_cycles.append(len(sample_frame_list) / frame_rate)
                                            if len(last_50_cycles) == zhenshu:
                                                standard_cycle = self.statistical_cycle_interval(last_50_cycles, zhenshu)

                                        elif len(last_50_cycles) == zhenshu:
                                            logger.info("周期列表个数{}，到{}，周期帧数限制".format(len(last_50_cycles), zhenshu))
                                            last_50_cycles.append(len(sample_frame_list) / frame_rate)
                                            last_50_cycles.pop(0)
                                            standard_cycle = self.statistical_cycle_interval(last_50_cycles, zhenshu)
                                            logger.info("落在多数区间的上限值是{}s".format(standard_cycle))
                                    except:
                                        logger.info(traceback.format_exc())
                                    try:
                                        # 大于标准周期的2倍，并且周期列表中有50个样本   不传输周期数据
                                        if len(sample_frame_list) >= standard_cycle * frame_rate * 2 and len(last_50_cycles) == zhenshu:
                                            logger.info("2倍区间上限是{}".format(standard_cycle * frame_rate * 2))
                                            logger.info("该样本帧数长度为{}".format(len(sample_frame_list)))
                                            # 将该样本的帧数据存入文件夹中
                                            with open("//usr//local//src//webroot//detecter//data//error_sample.csv", "w") as f:
                                                f.writelines("产品id为{}的帧数据量异常，存入异常文件中，不向Java传输周期数据".format(sample_id))
                                                for i in frame_bytes_list:
                                                    f.writelines(i)
                                                    f.writelines("\n")
                                                logger.info("成功写入异常帧数据！")
                                            f.close()
                                            logger.info("产品id为{}的帧数据量异常，存入异常文件中，不向Java传输周期数据".format(sample_id))
                                            frame_bytes_list = []
                                            del self.__pressure_frame_list[0:frame_list_len - 4]
                                            continue
                                    except:
                                        logger.info(traceback.format_exc())
                                        logger.info("产品id为{}的帧数据写入失败".format(sample_id))
                                        del self.__pressure_frame_list[0:frame_list_len - 4]
                                        continue

                                    frame_bytes_list = []
                                    try:
                                        # with open("//usr//local//src//webroot//detecter//data//test.csv", "a+") as f:
                                        #     f.writelines(str(sample_frame_list))
                                        #     f.writelines("\n")
                                        #     print("成功写入！")
                                        # f.close()
                                        pass
                                    except:
                                        logger.info(traceback.format_exc())

                                    # 这里进行时间序列降采样 变为10Hz
                                    try:
                                        df = pd.DataFrame(np.array(sample_frame_list))
                                        df.index = pd.to_datetime(pd.to_datetime(df[0]))
                                        df1 = df.resample('100L').last().ffill()
                                        df1[0] = df1.index
                                        df1[0] = df1[0].apply(lambda x: str(x))
                                        for _ in range(1, 11):
                                            df1[_] = df1[_].apply(lambda x: eval(x))
                                        sample_frame_list_downsampling = df1.resample('100L').last().ffill().values.tolist()
                                        # 收集一个产品，上传一个产品
                                        self.add_increment(len(sample_frame_list_downsampling) * 8)
                                        # print(len(sample_frame_list))
                                    except:
                                        # 程序在新增跳出时，一定要检查后面是否出现其他必须执行的操作！！！！！
                                        del self.__pressure_frame_list[0:frame_list_len - 4]
                                        logger.error(traceback.format_exc())
                                        continue

                                del self.__pressure_frame_list[0:frame_list_len - 4]
                        del_size = int(pressure_size / 2)
                    else:
                        # logger.error('Producer 数据校验出错: %s' % bytes2hex(frame_bytes))
                        # logger.error('Producer 数据校验出错: %s' % bytes2hex(frame_bytes))
                        del_size = 1  # 往后找,直到对上协议格式为止
                    del data_bytes[0:del_size]
            self.__data_bytes.clear()
            self.__pressure_frame_list.clear()
            self.__temp_frame_list.clear()
            self.stop()
            logger.warning("样本采集线程sample_producer_yuqidong退出")

    # 统计周期区间
    @staticmethod
    def statistical_cycle_interval(Last_50_cycles, zhenshu):  # 将周期列表、设置样本长度传过来
        # logger.info("周期列表个数{}，到{}，周期帧数限制".format(len(Last_50_cycles), zhenshu))
        dict_qujian = {}
        for i in Last_50_cycles:
            dict_qujian[str(i // 5)] = 0
        for i in Last_50_cycles:
            dict_qujian[str(i // 5)] += 1
        ans = max(dict_qujian, key=lambda x: dict_qujian[x])
        standard_cycle = (eval(ans) + 1) * 5
        # logger.info("落在多数区间的上限值是{}s".format(standard_cycle))
        return standard_cycle

    @staticmethod
    def add_increment(frame_length=None):
        update_product(UpdateType.ADD_GOOD, frame_length=frame_length)

    # 模拟及开关信号示例数据
    @staticmethod
    def is_pressure_frame(unknown_bytes):  # 0:帧头, 1:dest,3:数据长度, 17:帧尾
        check_bytes = unknown_bytes[1:18]  # 从第2字节到第16字节, 需要校验的字节段
        check_result = unknown_bytes[18]  # 第17字节, 异或校验和
        if unknown_bytes[0] == 0xAA and unknown_bytes[1] == 0xff and unknown_bytes[3] == 18 \
                and unknown_bytes[4] == 0x01 and unknown_bytes[19] == 0xAA:
            bbc = 0x00  # 0异或任何数为任何数本身
            for byte in check_bytes:
                bbc ^= byte
            return bbc == check_result
        else:
            return False

    # K型热电偶温度示例数据
    @staticmethod
    def is_temp_frame(unknown_bytes):
        check_bytes = unknown_bytes[1:13]
        check_result = unknown_bytes[13]
        if unknown_bytes[0] == 0xAA and unknown_bytes[1] == 0xff and unknown_bytes[3] == 13 \
                and unknown_bytes[4] == 0x02 and unknown_bytes[14] == 0xAA:
            bbc = 0x00  # 0异或任何数为任何数本身
            for byte in check_bytes:
                bbc ^= byte
            return bbc == check_result
        else:
            return False

    # 1后面5个0 才能确定是一个产品的结束
    # 10是开模，9是射退
    @staticmethod
    def is_product_begin(check_frames):
        current_frame = check_frames[0]
        check_frame_list = check_frames[1:5]
        if int(current_frame[10]) == 1:
            flag = True
            for frame in check_frame_list:
                if int(frame[10]) == 1:
                    flag = False
                    break
        else:
            flag = False
        return flag

    @staticmethod
    def calc_crc(string):
        data = bytearray.fromhex(string)
        # print(data)
        crc = 0xFFFF  # 初始值
        for pos in data:
            crc ^= pos  # 每次两位16进制和crc低8位异或
            for i in range(8):  # 右移八次
                if (crc & 1) != 0:  # 如果移出去的不是0（即移出去的是1）则右移1位且和A001异或。否则为0直接右移1位不作其他处理
                    crc >>= 1
                    crc ^= 0xA001
                else:
                    crc >>= 1
        ret = hex(((crc & 0xff) << 8) + (crc >> 8))[2:]  # 高低8位对调。
        if len(ret) != 4:
            ret = '0' * (4 - len(ret)) + ret
        return ret

    @staticmethod
    def transfer_pressure_frame_to_arr(frame_bytes):
        if MACHINE_CODE in ["A01", "A02", "A03", "A04", "A05"]:
            signal_0 = 1
            signal_1 = 0
        else:
            signal_0 = 0
            signal_1 = 1
        frame_arr = []
        # 日期 0
        frame_arr.append(datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')),  # 日期 0
        # 时间戳 1  毫秒级别
        frame_arr.append(int(round(time.time() * 1000)))  # 时间戳 1  毫秒级别
        # 帧序列号自行标记
        # 帧序列号 2
        frame_arr.append(0)  # 帧序列号,每秒80帧 2
        # 设定压力 3
        frame_arr.append(round(struct.unpack('<f', struct.pack('<I', eval('0x' + (frame_bytes[6:14]))))[0], 10))  # 设定压力 3
        # 设定速度 4
        frame_arr.append(round(struct.unpack('<f', struct.pack('<I', eval('0x' + (frame_bytes[14:22]))))[0], 10))  # 设定速度 4
        # 实际压力 5
        frame_arr.append(round(struct.unpack('<f', struct.pack('<I', eval('0x' + (frame_bytes[22:30]))))[0], 10))  # 实际压力 5
        # 螺杆位置 6
        frame_arr.append(round(struct.unpack('<f', struct.pack('<I', eval('0x' + (frame_bytes[30:38]))))[0], 10))  # 螺杆位置 6

        sj = signal_1
        if round(struct.unpack('<f', struct.pack('<I', eval('0x' + (frame_bytes[38:46]))))[0], 10) >= 22:
            sj = signal_0
        # 射胶信号 7
        frame_arr.append(sj)  # 射胶信号 7  # 电压值大于22V为1，小于2V为0

        cl = signal_1
        if round(struct.unpack('<f', struct.pack('<I', eval('0x' + (frame_bytes[46:54]))))[0], 10) >= 22:
            cl = signal_0
        # 储料信号 8
        frame_arr.append(cl)  # 储料信号 8

        st = signal_1
        if round(struct.unpack('<f', struct.pack('<I', eval('0x' + (frame_bytes[54:62]))))[0], 10) >= 22:
            st = signal_0
        # 射退信号 9
        frame_arr.append(st)  # 射退信号 9

        km = signal_1
        if round(struct.unpack('<f', struct.pack('<I', eval('0x' + (frame_bytes[62:70]))))[0], 10) >= 22:
            km = signal_0
        # 开模信号 10  # 电压值大于22V为1，小于2V为0
        frame_arr.append(km)
        return frame_arr

    @staticmethod
    def temp_frames_to_arr(tem_bytes):
        tem_arr = list()
        tem_arr.append(datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f'))  # 日期
        tem_arr.append(int(round(time.time() * 1000)))  # 时间戳
        tem_arr.append(tem_bytes[5] * 256 + tem_bytes[6])  # 第一段温度
        tem_arr.append(tem_bytes[7] * 256 + tem_bytes[8])  # 第二段温度
        tem_arr.append(tem_bytes[9] * 256 + tem_bytes[10])  # 第三段温度
        tem_arr.append(tem_bytes[11] * 256 + tem_bytes[12])  # 第三段温度
        return tem_arr


class UpdateType(Enum):
    ADD_GOOD = 2
    ADD_BAD = 3
    POS_FEEDBACK = 1  # 良品+1同时不良品-1
    NEG_FEEDBACK = -1  # 不良+1 同时良品-1


def bytes2hex(frame_bytes):
    hex_bytes = binascii.b2a_hex(frame_bytes)
    frame_bytes_str = hex_bytes.decode('utf-8').upper()
    return frame_bytes_str


def update_product(update_type=UpdateType.ADD_GOOD, frame_length=None):
    url = settings.SERVER_URL
    pad_url = '{}/pad/pad'.format(url)
    product_url = f"{pad_url}/updateNew/{settings.MACHINE_CODE}/{frame_length}"
    get(product_url, None)


def get(url, data):
    try:
        response = requests.get(url, data, timeout=(3.05, 27))
        # response = requests.put(url, data, timeout=(3.05, 27))
        OK_CODE = 200
        if response.status_code == OK_CODE:
            logger.info(f'{url}:{response.json()}')
            result = analyse_response(response.json())
            # by zxy on 20191114
            response.close()
            return result
        else:
            logger.error(f"{response},网络异常,无法成功访问接口")
    except Exception as e:
        logger.error(e)


def analyse_response(result):
    SUCCESS_CODE = 0
    if result["code"] == SUCCESS_CODE:
        return result["data"]
    else:
        logger.error(result['msg'])
